/**
 * Copyright(c) 2014 Xunton Solutions
 *
 * History:
 *   15-5-6 上午10:36 Created by dwzhang
 */
package com.jade.bss.business.order.impl;

import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.ejb.TransactionAttribute;
import javax.inject.Inject;
import javax.inject.Named;

import com.jade.bss.base.BssConstants;
import com.jade.bss.base.BssException;
import com.jade.bss.business.order.*;
import com.jade.bss.base.product.Product;
import com.jade.bss.base.product.ProductConstants;
import com.jade.bss.base.product.ProductManager;
import com.jade.framework.base.context.ApplicationContextUtils;
import com.jade.framework.base.criterion.Criteria;
import com.jade.framework.base.criterion.Restrictions;
import com.jade.framework.base.lang.StatusException;
import com.jade.framework.base.pattern.pipeline.Pipeline;
import com.jade.framework.base.util.PagingList;
import com.jade.framework.base.util.Parameters;
import com.jade.framework.base.util.SimpleParameters;
import com.jade.journal.Journal;
import com.jade.journal.JournalEntityManager;
import com.jade.journal.JournalUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Transactional;

/**
 * 订单管理器实现。
 *
 * @author <a href="mailto:dwzhang@xunton.com">dwzhang</a> 
 * @version 1.0 15-5-6 上午10:36
 */
@Named ("bss_orderManager")
public class OrderManagerImpl
        implements OrderManager
{
    /**
     * "before"类管道如果需要阻止相应的操作如create、cancel、update、remove等，请抛出OrderException
     * 否则请阻止一切异常抛出，包括运行时异常。其它类管道无此限制。
     */
    public static final String PIPELINE_BEFORE_CREATE = "before_create";
    public static final String PIPELINE_AFTER_CREATE = "after_create";
    public static final String PIPELINE_BEFORE_PAID = "before_paid";
    public static final String PIPELINE_AFTER_PAID = "after_paid";
    public static final String PIPELINE_BEFORE_UPDATE = "before_update";
    public static final String PIPELINE_AFTER_UPDATE = "after_update";
    public static final String PIPELINE_BEFORE_FINISH = "before_finish";
    public static final String PIPELINE_AFTER_FINISH = "after_finish";
    public static final String PIPELINE_BEFORE_CANCEL = "before_cancel";
    public static final String PIPELINE_AFTER_CANCEL = "after_cancel";
    public static final String PIPELINE_BEFORE_REMOVE = "before_remove";
    public static final String PIPELINE_AFTER_REMOVE = "after_remove";
    protected Log log = LogFactory.getLog(OrderManagerImpl.class);
    protected Map<String, Pipeline<OrderContext, BssException>> pipelineMap;
    protected JournalEntityManager journalManager;
    Journal journal = JournalUtils.getJournal(TraceRecord.NAME);
    private OrderDao orderDao;
    private ProductManager productManager;

    @Inject
    public void setOrderDao(@Named ("bss_orderDao") OrderDao orderDao)
    {
        this.orderDao = orderDao;
    }

    @Inject
    public void setProductManager(@Named ("bss_productManager") ProductManager productManager)
    {
        this.productManager = productManager;
    }

    public void setPipelineMap(Map<String, Pipeline<OrderContext, BssException>> pipelineMap)
    {
        this.pipelineMap = pipelineMap;
    }

    protected JournalEntityManager getEntityManager()
    {
        if (journalManager == null) {
            journalManager = ApplicationContextUtils.getBean("system_journalEntityManager");
        }
        return journalManager;
    }

    protected Pipeline<OrderContext, BssException> getPipeline(String name)
    {
        return pipelineMap == null ? null : pipelineMap.get(name);
    }

    protected void beforePipeline(String name, Order order)
            throws BssException
    {
        Pipeline<OrderContext, BssException> pipeline = getPipeline(name);
        if (pipeline != null) {
            pipeline.handle(new OrderContext(order));
        }
    }

    protected void afterPipeline(String name, Order order)
    {
        try {
            Pipeline<OrderContext, BssException> pipeline = getPipeline(name);
            if (pipeline != null) {
                pipeline.handle(new OrderContext(order));
            }
        }
        catch (Throwable t) {
            log.warn("[" + order.getId() + "]" + "Processing order by pipeline(" + name + ") error:", t);
        }
    }

    protected void handleException(Throwable t, TraceRecord record)
            throws BssException
    {
        BssException exception;
        if (t instanceof BssException) {
            exception = (BssException) t;
        }
        else {
            if (t instanceof StatusException) {
                exception = new BssException(((StatusException) t).getStatusCode(), t.getMessage(), t.getCause(),
                        ((StatusException) t).getArguments());
            }
            else {
                exception = new BssException(BssConstants.ERR_UNEXPECTED, t, t.getMessage());
            }
        }
        if (record != null) {
            record.setResult(false);
            record.setErrorCode(exception.getStatusCode());
            record.setErrorMessage(exception.getMessage());
        }
        throw exception;
    }

    protected TraceRecord createRecord(String operatorType, String operator, String op, long orderId,
                                       String remark)
    {
        TraceRecord record = new TraceRecord();
        Date now = new Date();
        record.setOrderId(orderId);
        record.setResult(true);
        record.setOpDate(now);
        record.setOperation(op);
        record.setOperatorType(operatorType);
        record.setOperator(operator);
        record.setRemark(remark);
        return record;
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void create(String operatorType, String operator, Order order)
            throws BssException
    {
        Date now = new Date();
        try {
            order.setCreationDate(now);
            order.setUpdateDate(now);
            List<OrderItem> items = order.getItems();
            int count = 0;
            double totalPrice = 0;
            for (OrderItem item : items) {
                Product product = productManager.getProduct(item.getProductId());
                if (product == null) {
                    throw new BssException(ProductConstants.ERR_PRODUCT_NOT_EXISTED, "product not existed", null,
                            item.getProductId());
                }
                item.setFeePrice(product.getFeeInfo().getPrice());
                item.setTotalPrice(item.getCount() * item.getFeePrice());
                totalPrice += item.getTotalPrice();
                count += item.getCount();
            }
            order.setItemCount(count);
            order.setTotalPrice(totalPrice);
            beforePipeline(PIPELINE_BEFORE_CREATE, order);
            orderDao.add(order);
            afterPipeline(PIPELINE_AFTER_CREATE, order);
            TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_CREATE, order.getId(),
                    null);
            journal.infoEntity(record);
        }
        catch (Throwable t) {
            handleException(t, null);
        }

        orderDao.add(order);
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void update(String operatorType, String operator, Order order)
            throws BssException
    {
        //check order status
        TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_UPDATE, order.getId(), null);
        try {
            order.setUpdateDate(new Date());
            beforePipeline(PIPELINE_BEFORE_UPDATE, order);
            orderDao.update(order);
            order = get(order.getId());
            afterPipeline(PIPELINE_AFTER_UPDATE, order);
        }
        catch (Throwable t) {
            handleException(t, record);
        }
        finally {
            journal.infoEntity(record);
        }
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void cancel(String operatorType, String operator, String remark, long... ids)
            throws BssException
    {
        for (long id : ids) {
            TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_CANCEL, id, remark);
            try {
                // check content status
                Order order = get(id);
                if (order.getStatus() >= Order.STATUS_PAID) {
                    throw new BssException(OrderConstants.ERR_INVALID_STATUS, "invalid status");
                }
                beforePipeline(PIPELINE_BEFORE_CANCEL, order);
                int count = updateStatus(order, Order.STATUS_CANCELLED, new int[]{Order.STATUS_CREATED});
                if (count > 0) {
                    order.setStatus(Order.STATUS_CANCELLED);
                    afterPipeline(PIPELINE_AFTER_CANCEL, order);
                }
                else {
                    throw new BssException(OrderConstants.ERR_EMPTY_UPDATE, "no record update");
                }
            }
            catch (Throwable t) {
                handleException(t, record);
            }
            finally {
                journal.infoEntity(record);
            }
        }
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void paid(String operatorType, String operator, long id, String remark)
            throws BssException
    {
        TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_PAY, id, remark);
        try {
            // check content status
            Order order = get(id);
            if (order.getStatus() >= Order.STATUS_PAID) {
                throw new BssException(OrderConstants.ERR_INVALID_STATUS, "invalid status");
            }
            beforePipeline(PIPELINE_BEFORE_PAID, order);
            int count = updateStatus(order, Order.STATUS_PAID, new int[]{Order.STATUS_CREATED});
            if (count > 0) {
                order.setStatus(Order.STATUS_PAID);
                afterPipeline(PIPELINE_AFTER_PAID, order);
            }
            else {
                throw new BssException(OrderConstants.ERR_EMPTY_UPDATE, "no record update");
            }
        }
        catch (Throwable t) {
            handleException(t, record);
        }
        finally {
            journal.infoEntity(record);
        }
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void finished(String operatorType, String operator, long id, String remark)
            throws BssException
    {
        TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_FINISH, id, remark);
        try {
            // check content status
            Order order = get(id);
            if (order.getStatus() < Order.STATUS_PAID) {
                throw new BssException(OrderConstants.ERR_INVALID_STATUS, "invalid status");
            }
            beforePipeline(PIPELINE_BEFORE_FINISH, order);
            int count = updateStatus(order, Order.STATUS_FINISHED, new int[]{Order.STATUS_PAID, Order.STATUS_SENT});
            if (count > 0) {
                order.setStatus(Order.STATUS_FINISHED);
                afterPipeline(PIPELINE_AFTER_FINISH, order);
            }
            else {
                throw new BssException(OrderConstants.ERR_EMPTY_UPDATE, "no record update");
            }
        }
        catch (Throwable t) {
            handleException(t, record);
        }
        finally {
            journal.infoEntity(record);
        }
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void remove(String operatorType, String operator, String remark, long... ids)
            throws BssException
    {
        for (long id : ids) {
            TraceRecord record = createRecord(operatorType, operator, TraceRecord.OP_REMOVE, id, remark);
            try {
                // check content status
                Order order = get(id);
                if (order.getStatus() >= Order.STATUS_PAID && order.getStatus() != Order.STATUS_CANCELLED) {
                    throw new BssException(OrderConstants.ERR_INVALID_STATUS, "invalid status");
                }
                beforePipeline(PIPELINE_BEFORE_CANCEL, order);
                orderDao.remove(order);
                afterPipeline(PIPELINE_AFTER_CANCEL, order);
            }
            catch (Throwable t) {
                handleException(t, record);
            }
            finally {
                journal.infoEntity(record);
            }
        }
    }

    protected int updateStatus(Order order, int status, int[] statuses)
            throws BssException
    {
        Parameters parameters = new SimpleParameters();
        parameters.setInt("status", status);
        parameters.setObject("updateDate", new Date());
        return orderDao.update(order, parameters, statuses);
    }

    public Order get(long id)
            throws BssException
    {
        return orderDao.get(id);
    }

    public PagingList<Order> query(long customerId, Criteria criteria)
    {
        return orderDao.query(customerId, criteria);
    }

    public PagingList<Order> query(Criteria criteria)
    {
        return orderDao.query(criteria);
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void addTrace(TraceRecord... records)
    {
        Date now = new Date();
        for (TraceRecord record : records) {
            if (record.getOpDate() == null) {
                record.setOpDate(now);
            }
            journal.infoEntity(record);
        }
    }

    public List<TraceRecord> listTrace(long orderId)
    {
        Criteria criteria = new Criteria(-1, -1);
        criteria.add(Restrictions.eq("orderId", orderId));
        criteria.addOrder(com.jade.framework.base.criterion.Order.desc("opDate"));
        return journalManager.queryEntity(TraceRecord.class, criteria);
    }

    @TransactionAttribute
    @Transactional (rollbackFor = Throwable.class)
    public void updateOrderStatus(long orderId, int orderStatus, double tradePrice)
    {
        orderDao.updateOrderStatus(orderId, orderStatus, tradePrice);
    }
}
